package com.github.ylt.protocol.http;

import com.github.ylt.conf.Configure;
import com.github.ylt.protocol.http.server.ServerImpl;
import com.github.ylt.utils.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Objects;

/**
 * request实现
 */
public class Request {
    private final int BUFFER_SIZE = 4096;
    private final byte CR = 0x0D;
    private final byte LF = 0x0A;
    private String startLine;
    private SocketChannel socketChannel;
    private InputStream inputStream;
    private OutputStream outputStream;
    private char[] chars = new char[BUFFER_SIZE];
    private int position = 0;
    private StringBuffer buffer;
    private Headers headers;

    public Request(InputStream rawInputStream, OutputStream rawOutputStream) {
        this.inputStream = rawInputStream;
        this.outputStream = rawOutputStream;
        do {
            try {
                startLine = readLine();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }while (StringUtils.isEmpty(startLine));
    }

    private void add(char c) {
        if(position == BUFFER_SIZE){
            buffer.append(chars);
            position = 0;
        }
        chars[position++] = c;
    }

    public String getProtocolLine(){
        return startLine;
    }
    public String readLine() throws IOException {
        boolean isLF = false,isCR = false;
        buffer = new StringBuffer();
        while (!isLF){
            int read = inputStream.read();
            if(read == -1){
                return null;
            }
            if(isCR){
                if(read == LF){
                    isLF = true;
                }else{
                    add((char) CR);
                    add((char)read);
                }
            }else{
                if(read == CR){
                    isCR = true;
                }else{
                    add((char) read);
                }
            }
        }
        buffer.append(chars,0,position);
        System.out.println(buffer.toString());
        return buffer.toString();
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    public Headers getHeaders() throws IOException {
        if(headers != null){
            return headers;
        }
        headers = new Headers();
        int read = -1,length = 0;
        char[] chars = new char[10];
        read = inputStream.read();
        if(read == CR || read == LF){
            int b = inputStream.read();
            if(b == CR || b == LF){
                return headers;
            }
            chars[0] = (char) b;
            length = 1;
            read = b;
        }
        while (read != CR && read != LF && read >= 0){
            int end = -1;
            int d;
            boolean key = read > 0x20;
            chars[length++] = (char) read;
            parse:{
                while ((d = inputStream.read()) >= 0){
                    if(d == 0x3A){
                        if(key && length > 0){
                            end = length;
                        }
                    }else if(d == 0x09){
                        d = 0x20;
                    }else if(d == 0x20){
                        key = false;
                    }else if(d == CR || d == LF){
                        read = inputStream.read();
                        if(d == CR && read == LF){
                            read = inputStream.read();
                            if(read == CR){
                                read = inputStream.read();
                            }
                        }
                        if(read == CR || read == LF || read > 0x20){
                            break parse;
                        }
                        d = 0x20;
                    }
                    if(length >= chars.length){
                        char[] ds = new char[chars.length * 2];
                        System.arraycopy(chars, 0, ds, 0, chars.length);
                        chars = ds;
                    }
                    chars[length++] = (char) d;
                }
                 read = -1;
            }
            while(length >= 0 && chars[length - 1] <= 0x20){
                length--;
            }
            String k;
            if(end <= 0){
                k = null;
                end = 0;
            }else{
                k = String.copyValueOf(chars, 0, end);
                if(end < length && chars[end] == 0x3A){
                    end++;
                }
                while (end < length && chars[end] == 0x20){
                    end++;
                }
            }
            String v;
            if(end >= length){
                v = "";
            }else{
                v = String.copyValueOf(chars, end, length-end);
            }
            if(headers.size() >= Configure.DEFAULT_MAX_REQUEST_HEADERS_SIZE){
                throw new IOException("请求头已经超出最大的数量请减少请求头的数量或者修改com.github.ylt.conf.Configure.DEFAULT_MAX_REQUEST_HEADERS_SIZE");
            }
            if(k == null){
                k = "";
            }
            headers.add(k,v);
            length = 0;
        }
        return new ImmutableHeaders(headers);
    }

    public static class ReadStream extends InputStream {
        SocketChannel channel;
        ByteBuffer byteBuf,markBuf;
        ServerImpl serverImpl;
        boolean closed = false,eof = false,marked = false,reset = false;
        static final int BUFFER_SIZE = 4 * 1024;
        int readlimit;
        static long readTimeOut;
        byte[] data_one;
        public ReadStream(SocketChannel channel, ServerImpl serverImpl) {
            this.serverImpl = serverImpl;
            this.channel = channel;
            this.byteBuf = ByteBuffer.allocate(BUFFER_SIZE);
            this.byteBuf.clear();
            closed = marked = reset = false;
            data_one = new byte[1];
        }

        @Override
        public synchronized int read() throws IOException {
            int read = read(data_one, 0, 1);
            if(read == 1){
                return data_one[0] & 0x0FF;
            }
            return -1;
        }

        public synchronized int read(byte[] b, int off, int len) throws IOException {
            int canreturn,willreturn;
            if (closed){
                throw new IOException("数据流已经关闭");
            }
            if(eof){
                return -1;
            }
            if(!channel.isBlocking()){
                throw new IOException("数据通道未阻塞，读取失败");
            }
            Objects.checkFromIndexSize(off, len, b.length);
            if(reset){
                canreturn = markBuf.remaining();
                willreturn = Math.min(canreturn, len);
                markBuf.get(b, off, willreturn);
                if(canreturn == willreturn){
                    reset = false;
                }
            }else{
                byteBuf.clear();
                if(len<BUFFER_SIZE){
                    byteBuf.limit(len);
                }
                do {
                    willreturn = channel.read(byteBuf);
                }while (willreturn == 0);
                if(willreturn == -1){
                    eof = true;
                    return -1;
                }
                byteBuf.flip();
                byteBuf.get(b, off, willreturn);
                if(marked){
                    try {
                        markBuf.put(b, off, willreturn);
                    }catch (BufferOverflowException e){
                        marked = false;
                    }
                }
            }
            return willreturn;
        }
        public synchronized int read(byte[] b) throws IOException {
            return read(b,0,b.length);
        }

        @Override
        public boolean markSupported() {
            return true;
        }

        public synchronized int available() throws IOException {
            if(closed){
                throw new IOException("数据流通道已经关闭");
            }
            if (eof){
                return -1;
            }
            if(reset){
                return markBuf.remaining();
            }
            return byteBuf.remaining();
        }

        @Override
        public void close() throws IOException {
            if(closed){
                return;
            }
            channel.close();
            closed = true;
        }

        @Override
        public void reset() throws IOException {
            if(closed){
              return;
            }
            if(!marked){
                throw new IOException("数据流未被标记，无法处理");
            }
            marked = false;
            reset = true;
            markBuf.flip();
        }

        @Override
        public synchronized void mark(int readlimit) {
            if(closed){
               return;
            }
            this.readlimit = readlimit;
            markBuf = ByteBuffer.allocate(readlimit);
            marked = true;
            reset = false;
        }

        public boolean isEof(){
            return eof;
        }
    }

    public static class WriteStream extends OutputStream {
        SocketChannel channel;
        ByteBuffer byteBuf;
        ServerImpl serverImpl;
        byte[] data_one;
        boolean closed = false;
        SelectionKey key;
        int BUFFER_SIZE = 4 * 1024;
        public WriteStream(SocketChannel channel, ServerImpl serverImpl) {
            this.serverImpl = serverImpl;
            this.channel = channel;
            this.data_one = new byte[1];
            this.byteBuf = ByteBuffer.allocate(BUFFER_SIZE);
        }

        @Override
        public synchronized void write(int b) throws IOException {
            data_one[0] = (byte) b;
            write(data_one,0,1);
        }

        public synchronized void write(byte[] b, int off, int len) throws IOException {
            int length = len;
            if(closed){
                throw new IOException("数据流通道已经关闭，无法写入");
            }
            System.out.println(channel.isOpen());
            System.out.println("正在写出。、。");
            int capacity = byteBuf.capacity();
            if(capacity < len){
                int diff = len - capacity;
                byteBuf = ByteBuffer.allocate(2*(capacity+diff));
            }
            byteBuf.clear();
            byteBuf.put(b, off, len);
            byteBuf.flip();
            int d;
            while ((d=channel.write(byteBuf))<length){
                length -= d;
                if(length == 0){
                    return;
                }
            }
        }

        public synchronized void write(byte[] b) throws IOException {
            write(b,0,b.length);
        }

        @Override
        public void close() throws IOException {
            if(closed){
                return;
            }
            channel.close();
            closed = true;
        }
    }
}
